﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/16 16:23:51 
* clrversion :4.0.30319.18444
******************************************************/

using Machine.DataAccess.Common;
using Machine.DataAccess.Linq.Relation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Machine.DataAccess.Linq
{
    class DbSetBind : ExpressionVisitor
    {
        int count = 0;

        Dictionary<Type, IEnumerable<ColumnDeclaration>> map = new Dictionary<Type, IEnumerable<ColumnDeclaration>>();
        bool isOrderBy = false;
        Type mainType { get; set; }
        Queue<TableExpression> joinSingles { get; set; }
        public DbSetBind()
        {
            this.joinSingles = new Queue<TableExpression>();
        }

        public ProjectionExpression Bind(Expression exp)
        {
            return base.Visit(exp) as ProjectionExpression;
            //return projecter;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            var table = c.Value as IQueryable;
            if (table != null) return this.GetTableExpression(c);
            return c;
        }

        private ProjectionExpression GetTableExpression(ConstantExpression exp)
        {
            var table = exp.Value as IQueryable;
            this.mainType = table.ElementType;
            var tableNum = this.count;
            this.count++;
            var feilds = table.ElementType.GetProperties();
            List<MemberBinding> bindings = new List<MemberBinding>();
            List<ColumnDeclaration> colums = new List<ColumnDeclaration>();
            foreach (var feild in feilds)
            {
                if (Type.GetTypeCode(feild.PropertyType) == TypeCode.Object && (feild.PropertyType != typeof(Guid) && feild.PropertyType != typeof(byte[]))) continue;
                if (feild.GetCustomAttributes(typeof(IgnoreColumnAttribute), true).Count() > 0) continue;
                var expressionTemp = new ColumExpression(feild.PropertyType, tableNum, feild.Name, table.ElementType.Name,"");
                bindings.Add(Expression.Bind(feild, expressionTemp));
                colums.Add(new ColumnDeclaration(feild.Name, expressionTemp));
            }

            map[table.ElementType] = colums;
            //this.count++;
            foreach (var feild in feilds)
            {
                if (Type.GetTypeCode(feild.PropertyType) == TypeCode.Object && feild.PropertyType != typeof(Guid))
                {
                    if (feild.PropertyType.IsGenericType && feild.PropertyType.GetGenericTypeDefinition() == typeof(ITable<>)) continue;
                    if (feild.PropertyType == typeof(byte[])) continue;
                    if (!this.map.ContainsKey(feild.PropertyType))
                    {
                        this.AddJoinSingle(feild.PropertyType, table.ElementType);
                        foreach (var item in map[feild.PropertyType])
                        {
                            colums.Add(item);
                        }
                    }
                }
            }
            var returnType = typeof(IQueryable<>).MakeGenericType(table.ElementType);
            var projector = Expression.MemberInit(Expression.New(table.ElementType), bindings.ToArray());

            //var tableNameAttribute = table.ElementType.GetCustomAttributes(typeof(DbTableAttribute), true).FirstOrDefault() as DbTableAttribute;
            string tableName = table.ElementType.GetTableName();//tableNameAttribute == null ? table.ElementType.Name : tableNameAttribute.TableName;

            var retItem = new ProjectionExpression(new SelectExpression(
                returnType,
                tableNum,
                colums,
                new TableExpression(table.ElementType, tableName, tableNum),
                null, null), projector);
            retItem.Selection.JoinSingleType = this.joinSingles;
            return retItem;
        }

        private void AddJoinSingle(Type type,Type baseType)
        {
            var tableNum = this.count;
            this.count++;
            var properties = type.GetProperties();
            List<MemberBinding> bindings = new List<MemberBinding>();
            List<ColumnDeclaration> colums = new List<ColumnDeclaration>();
            foreach (var feild in properties)
            {
                if (Type.GetTypeCode(feild.PropertyType) == TypeCode.Object && feild.PropertyType != typeof(Guid) && feild.PropertyType != typeof(byte[])) continue;
                var expressionTemp = new ColumExpression(feild.PropertyType, tableNum, feild.Name, type.Name, baseType.Name);
                bindings.Add(Expression.Bind(feild, expressionTemp));
                colums.Add(new ColumnDeclaration(feild.Name, expressionTemp));
            }
            string tableName = type.GetTableName();//tableNameAttribute == null ? type.Name : tableNameAttribute.TableName;
            this.joinSingles.Enqueue(new TableExpression(type, tableName, tableNum) { DeclareType = baseType });
            //this.count++;
            map[type] = colums;

            //foreach (var feild in properties)
            //{
            //    var typeCode = Type.GetTypeCode(feild.PropertyType);
            //    if (Type.GetTypeCode(feild.PropertyType) == TypeCode.Object && feild.PropertyType != typeof(Guid))
            //    {
            //        if (!this.map.ContainsKey(feild.PropertyType))
            //            this.AddJoinSingle(feild.PropertyType);
            //    }
            //}
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable) || m.Method.DeclaringType == typeof(QueryableExtession))
            {
                switch (m.Method.Name)
                {
                    case "Where":
                        return this.BindWhere(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                    case "Or":
                        return this.BindWhere(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]), true);
                    case "Like":
                        return this.BindLike(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                    case "Select":
                        return this.BindSelect(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                    case "Join":
                        if (m.Arguments.Count != 5) throw new NotSupportedException("暂时不支持重载方法");
                        return this.BindJoin(m.Type,
                            m.Arguments[0],
                            m.Arguments[1],
                            (LambdaExpression)StripQuotes(m.Arguments[2]),
                            (LambdaExpression)StripQuotes(m.Arguments[3]),
                            (LambdaExpression)StripQuotes(m.Arguments[4]));
                    case "Count":
                        return BindCount(m.Type, m.Arguments[0]);
                    case "OrderBy":
                        return this.BindOrderBy(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]), OrderByType.ASC);
                    case "FirstOrDefault":
                        if (m.Arguments.Count == 1) return this.BindFirstOrDefault(m.Type, m.Arguments[0]);
                        if (m.Arguments.Count == 2) return this.BindFirstOrDefault(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                        throw new NotImplementedException("暂时未实现");
                    case "Max":
                        if (m.Arguments.Count != 2) throw new NotSupportedException("不支持没指定的参数");
                        return this.BindMaxOrMinOrSum(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]), ColumType.Max);
                    case "Min":
                        if (m.Arguments.Count != 2) throw new NotSupportedException("不支持没指定的参数");
                        return this.BindMaxOrMinOrSum(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]), ColumType.Min);
                    case "Sum":
                        return this.BindMaxOrMinOrSum(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]), ColumType.Sum);
                    case "OrderByDescending":
                        return this.BindOrderBy(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]), OrderByType.DESC);
                    case "Take":
                        return BindTop(m.Type, m.Arguments[0], (ConstantExpression)m.Arguments[1]);
                    case "Skip":
                        return BindSkip(m.Type, m.Arguments[0], (ConstantExpression)m.Arguments[1]);
                    case "Include":
                        return BindInclude(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                    case "Distinct":
                        return this.BindDistinct(m.Type, m.Arguments[0]);
                    case "GeoDistance":
                        return this.BindGeoDistance(
                            m.Type, m.Arguments[0],
                            (LambdaExpression)StripQuotes(m.Arguments[1]),
                            (LambdaExpression)StripQuotes(m.Arguments[2]),
                            (LambdaExpression)StripQuotes(m.Arguments[3]),
                            (ConstantExpression)m.Arguments[4],
                            (ConstantExpression)m.Arguments[5]
                            //(ConstantExpression)m.Arguments[6]
                            );
                    case "Delete":
                        return this.BinDelete(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                    case "Update":
                        return this.BindUpdate(m.Type, m.Arguments[0],
                            (LambdaExpression)StripQuotes(m.Arguments[1]),
                            (LambdaExpression)StripQuotes(m.Arguments[2]));
                    //case "Union":
                    //    return this.BindUnion(m.Type, m.Arguments[0], m.Arguments[1]);
                    default:
                        throw new NotSupportedException(string.Format("存在不支持的方法:{0},请联系程序员增加支持方法", m.Method.Name));
                }
            }
            return m;
        }

        private Expression BindUnion(Type type, Expression source, Expression second)
        {
            var sourceExpression = this.Visit(source);
            var secondExpression = this.Visit(second);

            return sourceExpression;
        }

        private Expression BindGeoDistance(Type type, Expression source,
            LambdaExpression latExpression,
            LambdaExpression lngExpression,
            LambdaExpression distanceExpression,
            ConstantExpression lat,
            ConstantExpression lng)
        {
            var geoExpression = new GeoDistanceExpression(type);

            var sourceExpression = this.Visit(source) as ProjectionExpression;
            var columns = sourceExpression.Selection.Colums;

            var distanceProperty = (distanceExpression.Body as MemberExpression).Member as PropertyInfo;
            var distanceColumn = columns.FirstOrDefault(x => x.ColumExpression.DeclareTypeName == distanceProperty.DeclaringType.Name.ToLower() && x.ColumExpression.Name == distanceProperty.Name);
            geoExpression.OldTableIndex = distanceColumn.ColumExpression.TableIndex;
            distanceColumn.ColumExpression.TableIndex = this.count;
            geoExpression.TableIndex = this.count;
            geoExpression.DistanceName = distanceColumn.ColumName;

            geoExpression.Lat = (double)lat.Value;
            geoExpression.LatName = ((latExpression.Body as MemberExpression).Member as PropertyInfo).Name;
            geoExpression.Lng = (double)lng.Value;
            geoExpression.LngName = ((lngExpression.Body as MemberExpression).Member as PropertyInfo).Name;
            geoExpression.TableType = distanceProperty.DeclaringType;

            sourceExpression.Selection.GeoDistance = geoExpression;
            this.count++;

            return sourceExpression;
        }

        private Expression BindDistinct(Type type, Expression source)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            sourceExpression.Selection.IsDistinct = true;
            //sourceExpression.Selection.Colums = new List<ColumnDeclaration>();
            return sourceExpression;
        }

        private Expression BindInclude(Type type, Expression source, LambdaExpression lamb)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;

            var body = lamb.Body as MemberExpression;
            if (body == null) throw new Exception("表达式异常");
            var property = body.Member as PropertyInfo;
            if (property == null || !property.PropertyType.IsClass) throw new Exception("类型异常，不是表结构");

            this.AddJoinSingle(property.PropertyType, property.DeclaringType);

            var currentColums = sourceExpression.Selection.Colums.ToList();
            currentColums.AddRange(this.map[property.PropertyType]);
            sourceExpression.Selection.Colums = currentColums;

            while (this.joinSingles.Count > 0)
            {
                var joinSingle = this.joinSingles.Dequeue();
                if (sourceExpression.Selection.JoinSingleType.FirstOrDefault(x => x.TableName == joinSingle.TableName && x.DeclareType == joinSingle.DeclareType) != null)
                {
                    continue;
                }
                List<ColumnDeclaration> newCloums = new List<ColumnDeclaration>();
                newCloums.AddRange(sourceExpression.Selection.Colums);
                foreach (var item in this.map[joinSingle.Type])
                {
                    if (!newCloums.Contains(item)) newCloums.Add(item);
                }
                //newCloums.AddRange(this.map[joinSingle.Type]);
                sourceExpression.Selection.Colums = newCloums;
                List<TableExpression> types = new List<TableExpression>();
                types.AddRange(sourceExpression.Selection.JoinSingleType);
                types.Add(joinSingle);
                sourceExpression.Selection.JoinSingleType = types;
            }
            //var test = this.map[property.PropertyType];

            return sourceExpression;
        }

        private Expression BindCount(Type type, Expression source)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            sourceExpression.Selection.IsCount = true;
            //sourceExpression.Selection.Colums = new List<ColumnDeclaration>();
            return sourceExpression;
        }

        private Expression BindSkip(Type type, Expression source, ConstantExpression c)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            if (isOrderBy == false) throw new NotSupportedException("没有排序的集合,不支持");
            sourceExpression.Selection.Skip = (int)c.Value;
            return sourceExpression;
        }

        private Expression BindMaxOrMinOrSum(Type type, Expression source, LambdaExpression lamb, ColumType columType)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            //var selectColumns = this.GetSelectColumnDeclaration(lamb.Body);

            var memberExpression = lamb.Body as MemberExpression;
            var property = memberExpression.Member as PropertyInfo;

            var select = sourceExpression.Selection.Colums.FirstOrDefault(x => x.ColumName.ToLower() == property.Name.ToLower());

            select.ColumExpression.ColumType = columType;

            sourceExpression.Selection.Colums = new List<ColumnDeclaration>() { select };

            //foreach (var item in selectColumns)
            //{
            //    item.ColumExpression.ColumType = columType;
            //}
            //sourceExpression.Selection.Colums = selectColumns;
            return sourceExpression;
        }

        private Expression BindFirstOrDefault(Type type, Expression source, LambdaExpression lamb = null)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            sourceExpression.Selection.Top = 1;
            var where = this.Visit(lamb);
            if (sourceExpression.Selection.Where == null)
            {
                sourceExpression.Selection.Where = where;
            }
            else if (where != null)
            {
                sourceExpression.Selection.Where = Expression.And(sourceExpression.Selection.Where, where);
            }
            while (this.joinSingles.Count > 0)
            {
                var joinSingle = this.joinSingles.Dequeue();
                if (sourceExpression.Selection.JoinSingleType.FirstOrDefault(x => x.TableName == joinSingle.TableName && x.DeclareType == joinSingle.DeclareType) != null)
                {
                    continue;
                }
                List<ColumnDeclaration> newCloums = new List<ColumnDeclaration>();
                newCloums.AddRange(sourceExpression.Selection.Colums);
                foreach (var item in this.map[joinSingle.Type])
                {
                    if (!newCloums.Contains(item)) newCloums.Add(item);
                }
                //newCloums.AddRange(this.map[joinSingle.Type]);
                sourceExpression.Selection.Colums = newCloums;
                List<TableExpression> types = new List<TableExpression>();
                types.AddRange(sourceExpression.Selection.JoinSingleType);
                types.Add(joinSingle);
                sourceExpression.Selection.JoinSingleType = types;
            }
            return sourceExpression;
        }

        private Expression BindOrderBy(Type type, Expression source, LambdaExpression lamb, OrderByType orderByType)
        {
            this.isOrderBy = true;
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            var orderbyExpression = this.GetSelectColumnDeclaration(this.Visit(lamb.Body));
            sourceExpression.Selection.OrderBy = orderbyExpression;
            sourceExpression.Selection.OrderByType = orderByType;
            while (this.joinSingles.Count > 0)
            {
                var joinSingle = this.joinSingles.Dequeue();
                if (sourceExpression.Selection.JoinSingleType.FirstOrDefault(x => x.TableName == joinSingle.TableName && x.DeclareType == joinSingle.DeclareType) != null)
                {
                    continue;
                }
                List<ColumnDeclaration> newCloums = new List<ColumnDeclaration>();
                newCloums.AddRange(sourceExpression.Selection.Colums);

                foreach (var item in this.map[joinSingle.Type])
                {
                    if (!newCloums.Contains(item)) newCloums.Add(item);
                }
                //newCloums.AddRange(this.map[joinSingle.Type]);
                sourceExpression.Selection.Colums = newCloums;
                List<TableExpression> types = new List<TableExpression>();
                types.AddRange(sourceExpression.Selection.JoinSingleType);
                types.Add(joinSingle);
                sourceExpression.Selection.JoinSingleType = types;
            }
            return sourceExpression;
            //var orderTemp = new OrderByExpression(sourceExpression.Type, sourceExpression.Selection.From, orderbyExpression);
            //return new ProjectionExpression(new SelectExpression(sourceExpression.Selection.Type,
            //    sourceExpression.Selection.TableIndex,
            //    sourceExpression.Selection.Colums,
            //    sourceExpression.Selection.From, sourceExpression.Selection.Where,orderbyExpression), sourceExpression);
        }

        private Expression BindTop(Type type, Expression source, ConstantExpression c = null)
        {
            var project = this.Visit(source) as ProjectionExpression;
            if (c == null) project.Selection.Top = count;
            else project.Selection.Top = (int)c.Value;
            return project;
            //return new ProjectionExpression(new SelectExpression(
            //    project.Type,
            //    project.Selection.TableIndex,
            //    new List<ColumnDeclaration>() { },
            //    project.Selection.From, project.Selection.Where, project.Selection.OrderBy), project.Projection);
        }
        private Expression BindLike(Type type, Expression source, LambdaExpression lamb)
        {
            var project = this.Visit(source) as ProjectionExpression;
            var like = new LikeExpression(type, this.Visit(lamb.Body));
            if (project.Selection.Like == null)
            {
                project.Selection.Like = like;
            }
            else
            {
                var left = project.Selection.Like as LikeExpression;
                project.Selection.Like = Expression.And(left.Source, like.Source);
            }
            while (this.joinSingles.Count > 0)
            {
                var joinSingle = this.joinSingles.Dequeue();
                if (project.Selection.JoinSingleType.FirstOrDefault(x => x.TableName == joinSingle.TableName) != null)
                {
                    continue;
                }
                List<ColumnDeclaration> newCloums = new List<ColumnDeclaration>();
                newCloums.AddRange(project.Selection.Colums);
                foreach (var item in this.map[joinSingle.Type])
                {
                    if (!newCloums.Contains(item)) newCloums.Add(item);
                }
                //newCloums.AddRange(this.map[joinSingle.Type]);
                project.Selection.Colums = newCloums;
                List<TableExpression> types = new List<TableExpression>();
                types.AddRange(project.Selection.JoinSingleType);
                types.Add(joinSingle);
                project.Selection.JoinSingleType = types;
            }
            return project;
        }
        private Expression BindWhere(Type type, Expression source, LambdaExpression lamb,bool isOr = false)
        {
            var project = this.Visit(source) as ProjectionExpression;
            var where = this.Visit(lamb.Body);
            if (project.Selection.Where == null)
            {
                project.Selection.Where = where;
            }
            else
            {
                if (isOr)
                {
                    project.Selection.Where = Expression.Or(project.Selection.Where, where);
                }
                else
                {
                    project.Selection.Where = Expression.And(project.Selection.Where, where);
                }
            }
            while (this.joinSingles.Count > 0)
            {
                var joinSingle = this.joinSingles.Dequeue();
                if (project.Selection.JoinSingleType.FirstOrDefault(x => x.TableName == joinSingle.TableName) != null)
                {
                    continue;
                }
                List<ColumnDeclaration> newCloums = new List<ColumnDeclaration>();
                newCloums.AddRange(project.Selection.Colums);
                foreach (var item in this.map[joinSingle.Type])
                {
                    if (!newCloums.Contains(item)) newCloums.Add(item);
                }
                //newCloums.AddRange(this.map[joinSingle.Type]);
                project.Selection.Colums = newCloums;
                List<TableExpression> types = new List<TableExpression>();
                types.AddRange(project.Selection.JoinSingleType);
                types.Add(joinSingle);
                project.Selection.JoinSingleType = types;
            }
            return project;
        }

        private Expression BinDelete(Type type, Expression source,LambdaExpression lamb)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            sourceExpression.Selection.IsDelete = true;
            var where = this.Visit(lamb.Body);
            if (sourceExpression.Selection.Where == null)
            {
                sourceExpression.Selection.Where = where;
            }
            //sourceExpression.Selection.Colums = new List<ColumnDeclaration>();
            return sourceExpression;
        }

        private static object binaryRightLock = new object();
        protected override Expression VisitBinary(BinaryExpression b)
        {
            Expression left = this.Visit(b.Left);

            //var field = m.Member as FieldInfo;
            //LambdaExpression lamb = Expression.Lambda(m);
            //var fn = lamb.Compile();
            //return Expression.Constant(fn.DynamicInvoke(null), field.FieldType);
            //Expression right = this.Visit(b.Right);
            //Expression right = b.Right.NodeType == ExpressionType.Convert ?
            //    (b.Right as UnaryExpression).Operand :
            //    b.Right;

            Expression right = b.Right;

            lock (binaryRightLock)
            {
                if (right is MemberExpression)
                {
                    var m = right as MemberExpression;
                    if (m.Member is FieldInfo)
                    {
                        //var field = m.Member as FieldInfo;
                        //LambdaExpression lamb = Expression.Lambda(m);
                        //var fn = lamb.Compile();
                        //right = Expression.Constant(fn.DynamicInvoke(null), field.FieldType);
                        right = RightFieldInfoBinaryAccess(m);
                    }
                    else if (m.Member is PropertyInfo)
                    {
                        //var property = m.Member as PropertyInfo;
                        //LambdaExpression lamb = Expression.Lambda(m);
                        //var fn = lamb.Compile();
                        //right = Expression.Constant(fn.DynamicInvoke(null), property.PropertyType);
                        right = RightPropertyInfoBinaryAccess(m);
                    }
                }
                else if (right.NodeType == ExpressionType.Call)
                {
                    var call = right as MethodCallExpression;
                    //var method = call.Method;
                    //var lamb = Expression.Lambda(call);
                    //var fn = lamb.Compile();
                    //right = Expression.Constant(fn.DynamicInvoke(null), method.ReturnType);

                    right = RightMethodCallBinaryAccess(call);
                }
                else
                {
                    right = this.Visit(b.Right);
                }
            }

            //Expression right = this.Visit(b.Right);
            Expression conversion = this.Visit(b.Conversion);
            if (left != b.Left || right != b.Right || conversion != b.Conversion)
            {
                if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null)
                    return Expression.Coalesce(left, right, conversion as LambdaExpression);
                else
                    return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
            }
            return b;
        }

        private static Expression RightFieldInfoBinaryAccess(MemberExpression m)
        {
            var field = m.Member as FieldInfo;
            LambdaExpression lamb = Expression.Lambda(m);
            var fn = lamb.Compile();
            return Expression.Constant(fn.DynamicInvoke(null), field.FieldType);
        }

        private static Expression RightPropertyInfoBinaryAccess(MemberExpression m)
        {
            var property = m.Member as PropertyInfo;
            LambdaExpression lamb = Expression.Lambda(m);
            var fn = lamb.Compile();
            return Expression.Constant(fn.DynamicInvoke(null), property.PropertyType);
        }

        private static Expression RightMethodCallBinaryAccess(MethodCallExpression call)
        {
            //var call = b.Right as MethodCallExpression;
            var method = call.Method;
            var lamb = Expression.Lambda(call);
            var fn = lamb.Compile();
            return Expression.Constant(fn.DynamicInvoke(null), method.ReturnType);
        }

        private Expression BindSelect(Type type, Expression source, LambdaExpression selector)
        {
            var project = this.Visit(source) as ProjectionExpression;
            //var tableArg = string.Format("t{0}", this.count);
            // temp = this.Visit(selector.Body);
            var temp = this.Visit(selector.Body);
            MemberExpression memberAccess = temp as MemberExpression;
            if (memberAccess != null)
            {
                var parentTypeName = memberAccess.Member.DeclaringType.Name.ToLower();
                var colums = this.GetSelectColumnDeclaration(memberAccess).Where(x => x.ColumExpression.ParentTypeName == parentTypeName).ToList();
                foreach (var item in colums)
                {
                    item.ColumExpression.ParentTypeName = null;
                }
                project.Selection.Colums = colums;
            }
            else if (temp.NodeType == ExpressionType.New)
            {
                var colum = this.GetSelectColumnDeclaration(temp);
                project.Selection.Colums = colum;
            }
            else
            {
                var colum = temp as ColumExpression;
                project.Selection.Colums = new List<ColumnDeclaration>() { new ColumnDeclaration(colum.Name, colum) };
            }
            return project;
            //return new ProjectionExpression(new SelectExpression(type, tableArg, colums, project.Selection, null), project);
            //throw new NotImplementedException();
        }

        private Expression BindJoin(Type type, Expression left, Expression right, LambdaExpression leftKey, LambdaExpression rightKey, LambdaExpression selectExpression)
        {
            var leftProject = this.Visit(left) as ProjectionExpression;
            var leftColum = this.Visit(leftKey.Body) as ColumExpression;
            //this.count++;

            var rightProject = this.Visit(right) as ProjectionExpression;
            var rightColum = this.Visit(rightKey.Body) as ColumExpression;
            var on = Expression.Equal(leftColum, rightColum);

            var select = this.GetJoinColumDeclaration(selectExpression.Body, leftProject.Selection, rightProject.Selection);
            Expression where = null;
            if (leftProject.Selection.Where == null && rightProject.Selection.Where == null) where = null;
            else if (leftProject.Selection.Where == null && rightProject.Selection.Where != null) where = rightProject.Selection.Where;
            else if (leftProject.Selection.Where != null && rightProject.Selection.Where == null) where = leftProject.Selection.Where;
            else if (leftProject.Selection.Where != null && rightProject.Selection.Where != null) where = Expression.And(leftProject.Selection.Where, rightProject.Selection.Where);

            List<ColumnDeclaration> orderby = new List<ColumnDeclaration>();
            if (leftProject.Selection.OrderBy != null) orderby.AddRange(leftProject.Selection.OrderBy);
            if (rightProject.Selection.OrderBy != null) orderby.AddRange(rightProject.Selection.OrderBy);
            //this.count++;
            var join = new JoinExpression(type, leftProject.Selection.From, rightProject.Selection.From, on);
            //var test = new ProjectionExpression(
            //    new SelectExpression(type,
            //        this.count,
            //        select,
            //        leftProject.Selection.From, where, orderby), JoinSelect(type));
            var test = new ProjectionExpression(
                new SelectExpression(type,
                    this.count,
                    select,
                    leftProject.Selection.From, where, orderby), JoinSelect(type));
            var jj = new List<TableExpression>(test.Selection.JoinSingleType);
            jj.Add(rightProject.Selection.From as TableExpression);
            //jj.Add(leftProject.Selection.From as TableExpression);
            test.Selection.JoinSingleType = jj;
            return test;
        }

        private Expression JoinSelect(Type type)
        {
            var newType = type.GetGenericArguments()[0];
            if(!newType.Name.StartsWith("<>"))
            {
                var constructor1 = newType.GetConstructor(new Type[] { });
                return Expression.New(constructor1);
            }
            var properties = newType.GetProperties();
            var constructor = newType.GetConstructor(properties.Select(x => x.PropertyType).ToArray());
            List<ParameterExpression> bindings = new List<ParameterExpression>();
            foreach (var property in properties)
            {
                var parameter = Expression.Parameter(property.PropertyType, property.Name);
                bindings.Add(parameter);
            }
            return Expression.New(constructor, bindings.ToArray());
            //return Expression.MemberInit(newObj, bindings.ToArray());
        }

        private IEnumerable<ColumnDeclaration> GetSelectColumnDeclaration(Expression exp)
        {
            List<ColumnDeclaration> list = new List<ColumnDeclaration>();
            if (exp.NodeType == ExpressionType.New)
            {
                var objTemp = exp as NewExpression;
                foreach (var item in objTemp.Arguments)
                {
                    if (item.NodeType == (ExpressionType)ProjectionType.Colum)
                    {
                        list.Add(new ColumnDeclaration(((ColumExpression)item).Name, (ColumExpression)item));
                    }
                    else if (item.NodeType == ExpressionType.Parameter)
                    {
                        list.AddRange(this.map[(item as ParameterExpression).Type]);
                    }
                    else if (item.NodeType == ExpressionType.MemberAccess)
                    {
                        var temp = item as MemberExpression;
                        //var property = temp.Member as PropertyInfo;
                        list.Add(this.map[temp.Member.DeclaringType].FirstOrDefault(x => x.ColumName == temp.Member.Name));
                    }
                }
            }
            else if (exp.NodeType == (ExpressionType)ProjectionType.Colum)
            {
                var temp = exp as ColumExpression;
                list.Add(new ColumnDeclaration(temp.Name, temp));
            }
            else if (exp.NodeType == ExpressionType.MemberAccess)
            {
                var temp = exp as MemberExpression;
                var property = temp.Member as PropertyInfo;
                list.AddRange(this.map[property.PropertyType]);
                var allProperties = property.PropertyType.GetProperties();
                foreach (var item in allProperties)
                {
                    if (this.map.ContainsKey(item.PropertyType))
                    {
                        list.AddRange(this.map[item.PropertyType]);
                    }
                }
                //list.Add(this.map[property.PropertyType].FirstOrDefault(x => x.ColumName == temp.Member.Name));
            }
            return list;
        }

        private IEnumerable<ColumnDeclaration> GetJoinColumDeclaration(Expression select, SelectExpression leftColum, SelectExpression rightColum)
        {
            List<ColumnDeclaration> list = new List<ColumnDeclaration>();
            if (select.NodeType == ExpressionType.New)
            {
                var selectTemp = select as NewExpression;
                foreach (var item in selectTemp.Arguments)
                {
                    if (item.NodeType == ExpressionType.MemberAccess)
                    {
                        var b = item as MemberExpression;
                        //list.Add(JoinSelectHelp(item as MemberExpression, leftColum.Type.GetGenericArguments()[0], rightColum.Type.GetGenericArguments()[0]));
                        list.Add(JoinSelectHelp(item as MemberExpression, b.Member.ReflectedType));

                    }
                    else if (item.NodeType == ExpressionType.Parameter)
                    {
                        if (item.Type == leftColum.Type.GetGenericArguments()[0])
                        {
                            list.AddRange(leftColum.Colums);
                        }
                        else
                        {
                            list.AddRange(rightColum.Colums);
                        }
                    }
                    else throw new NotSupportedException("未考虑的处理");
                }
            }
            else if (select.NodeType == ExpressionType.MemberAccess)
            {
                var selectTemp = select as MemberExpression;
                list.Add(JoinSelectHelp(selectTemp, leftColum.Type.GetGenericArguments()[0], rightColum.Type.GetGenericArguments()[0]));
            }
            else if(select.NodeType == ExpressionType.Parameter)
            {
                var item = select as ParameterExpression;
                if (item.Type == leftColum.Type.GetGenericArguments()[0])
                {
                    list.AddRange(leftColum.Colums);
                }
                else
                {
                    list.AddRange(rightColum.Colums);
                }
            }
            return list;
        }

        private ColumnDeclaration JoinSelectHelp(MemberExpression selectTemp, params Type[] types)
        {
            foreach (var type in types)
            {
                //var temp = this.map[type].FirstOrDefault(x => x.ColumName == selectTemp.Member.Name);
                //if (temp != null)
                //{
                //    var columTemp = temp.ColumExpression as ColumExpression;
                //    return new ColumnDeclaration(columTemp.Name, columTemp.GetWrapColumExpression());
                //}
                return this.map[type].FirstOrDefault(x => x.ColumName == selectTemp.Member.Name);
            }
            throw new NotImplementedException();
        }

        //private IEnumerable<ColumnDeclaration> JoinSelectHelp(IEnumerable<ColumnDeclaration> colums)
        //{
        //    List<ColumnDeclaration> list = new List<ColumnDeclaration>();
        //    foreach (var item in colums)
        //    {
        //        var temp = item.ColumExpression as ColumExpression;
        //        list.Add(new ColumnDeclaration(item.ColumName,
        //            new ColumExpression(temp.Type, temp.TableIndex + 1, temp.Name)));
        //    }
        //    return list;
        //}

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }

        private Type GetBaseType(MemberExpression m)
        {
            var temp = (m.Expression as MemberExpression);
            if (temp != null) return temp.Member.ReflectedType;
            var parameter = (m.Expression as ParameterExpression);
            if (parameter != null) return parameter.Type;
            return null;
        }

        protected override Expression VisitMember(MemberExpression m)
        {
            var baseType = GetBaseType(m);

            var property = m.Member as PropertyInfo;
            if (property != null && property.ReflectedType == typeof(DateTime)) return m;
            if (property != null && this.map.ContainsKey(property.ReflectedType))
            {
                //var type = this.map.ContainsKey(property.ReflectedType) ? property.ReflectedType : this.mainType;
                if (this.map.ContainsKey(property.PropertyType)) return m;
                return this.map[property.ReflectedType].FirstOrDefault(x => x.ColumName == property.Name).ColumExpression as ColumExpression;
            }
            else if (m.Member.DeclaringType.Name.StartsWith("<>"))
            {
                var field = m.Member as FieldInfo;
                if (field != null)
                {
                    LambdaExpression lamb = Expression.Lambda(m);
                    var fn = lamb.Compile();
                    return Expression.Constant(fn.DynamicInvoke(null), field.FieldType);
                }
                
            }
            else if (m.Member.DeclaringType != this.mainType)
            {
                if (this.mainType.BaseType == m.Member.DeclaringType)
                {
                    return this.map[this.mainType].FirstOrDefault(x => x.ColumName == property.Name).ColumExpression as ColumExpression;
                }
                if (m.Member.DeclaringType.IsGenericType)
                {
                    var typeTemp = m.Member.DeclaringType.GetGenericArguments()[0];
                    if (!this.map.ContainsKey(typeTemp))
                        this.AddJoinSingle(typeTemp, baseType);
                    return this.map[typeTemp].FirstOrDefault(x => x.ColumName == m.Member.Name).ColumExpression;
                }
                if (!this.map.ContainsKey(m.Member.DeclaringType))
                {
                    this.AddJoinSingle(m.Member.DeclaringType, baseType);
                }
                return this.map[m.Member.DeclaringType].FirstOrDefault(x => x.ColumName == m.Member.Name).ColumExpression;
                //var type = m.Member.DeclaringType;
                //var properties = type.GetProperties();
                //List<MemberBinding> bindings = new List<MemberBinding>();
                //List<ColumnDeclaration> colums = new List<ColumnDeclaration>();
                //foreach (var feild in properties)
                //{
                //    if (Type.GetTypeCode(feild.PropertyType) == TypeCode.Object && feild.PropertyType != typeof(Guid)) continue;
                //    var expressionTemp = new ColumExpression(feild.PropertyType, this.count, feild.Name,m.Member.DeclaringType.Name);
                //    bindings.Add(Expression.Bind(feild, expressionTemp));
                //    colums.Add(new ColumnDeclaration(feild.Name, expressionTemp));
                //}
                //this.count++;
                //map[type] = colums;
                //var tableNameAttribute = m.Member.DeclaringType.GetCustomAttributes(typeof(DbTableAttribute), true).FirstOrDefault() as DbTableAttribute;
                //string tableName = tableNameAttribute == null ? m.Member.DeclaringType.Name : tableNameAttribute.TableName;
                //this.joinSingles.Enqueue(new TableExpression(m.Member.DeclaringType, tableName, this.count - 1));
                //return colums.FirstOrDefault(x => x.ColumName == m.Member.Name).ColumExpression;
            }
            throw new NotSupportedException();
        }

        private Expression BindUpdate(Type type, Expression source, LambdaExpression lamb,LambdaExpression wherelamba)
        {
            var sourceExpression = this.Visit(source) as ProjectionExpression;
            sourceExpression.Selection.IsUpdate = true;
            var select = this.Visit(lamb.Body);
            sourceExpression.Selection.UpdateSelect = new UpdateExpression(type,select);

            //sourceExpression.Selection.Colums = select.Selection.Colums;
            var where = this.Visit(wherelamba.Body);
            sourceExpression.Selection.Where = where;
            //sourceExpression.Selection.Colums = new List<ColumnDeclaration>();
            return sourceExpression;
        }
    }
}
